#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <GL/glut.h>

#include <port_comm.h>

#define VIEWPORT_WIDTH 640
#define VIEWPORT_HEIGHT 480
#define KEY_Q 113

int initGL();

GLvoid OnDisplay();
GLvoid OnReshape(GLint w, GLint h);
GLvoid OnKeyPress(unsigned char key, GLint x, GLint y);
GLvoid OnIdle();
void printUsage();
cv::Mat flipMatrix(cv::Mat mat);

GLint g_hWindow;
PortComm *commServer;
char* dest;
int camera = 0;
int quality = 50;
bool invert = true;
bool getImage = false;

int main(int argc, char *argv[])
{

    //Parse arguemtns
    if (argc != 2 && argc != 3)
    {
        printUsage();
        return 1;
    }

    int portno = atoi(argv[1]);

    commServer = new PortComm("localhost", portno, PortComm::SERVER);
    printf("Init connection complete.\n");

    const char* file;
    if(argc == 2)
    {
        file = "/tmp";
    }
    else
    {
        file = argv[2];
    }

    if(file[strlen(file)-1] != '/')
    {
        //Cat a / on to the end
        char* new_file = new char[strlen(file) + 1];
        strcpy (new_file, file);

        strcat (new_file, "/");
        file = new_file; 
    }
    const char* name = "test.jpg";
    dest = new char[strlen(file) + strlen(name) + 1];
    strcpy (dest, file);
    strcat (dest, name);
    printf("Saving to %s\n", dest);

    //Init GL
    glutInit(&argc, argv);
    if(initGL() != 0)
    {
        fprintf(stderr, "Unable to init GL Component.\n");
        return 1;
    }

    printf("Init GL complete.\n");

    quality = 95;

    //Run event loop
    glutMainLoop();
    return 0;
}

int initGL()
{
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);

    g_hWindow = glutCreateWindow("High Quality");

    glClearColor(0.0, 0.0, 0.0, 0.0);

    glutDisplayFunc(OnDisplay);
    glutReshapeFunc(OnReshape);
    glutKeyboardFunc(OnKeyPress);
    glutIdleFunc(OnIdle);

    return 0;
}

GLvoid OnDisplay()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0, VIEWPORT_WIDTH, VIEWPORT_HEIGHT, 0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  
    glBegin(GL_QUADS);
    glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex2f(VIEWPORT_WIDTH, 0.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex2f(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
    glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, VIEWPORT_HEIGHT);
    glEnd();

    glFlush();
    glutSwapBuffers();
}

GLvoid OnReshape(GLint w, GLint h)
{
    glViewport(0, 0, w, h);
}

GLvoid OnKeyPress(unsigned char key, int x, int y)
{
    if(key >= '0' && key <= '9')
    {
        camera = key - '0';
    }

    switch(key)
    {
        case 'l':
            quality++;
            if(quality > 100)
            {
                quality = 100;
            }
            break;
        case 'k':
            quality += 10;
            if(quality > 100)
            {
                quality = 100;
            }
            break;
        case 'h':
            quality--;
            if(quality < 0)
            {
                quality = 0;
            }
            break;
        case 'j':
            quality -= 10;
            if(quality < 0)
            {
                quality = 0;
            }
            break;
        case ' ':
            invert = !invert;
            break;
        case '.':
            getImage = true;
            break;
    }

    printf("camera = %d\nquality = %d\ninvert = %d\n\n", camera, quality, invert);

    glutPostRedisplay();
}

GLvoid OnIdle()
{
    if(getImage)
    {
        getImage = false;
        cv::Mat matrix;
        int *mode = new int[2];
        mode[0] = camera;
        mode[1] = quality;
        commServer->write_port((unsigned char *) mode , 2 * sizeof(int));
   
        int size;
        unsigned char *message;
        commServer->read_port(&message, &size);

        if(size == 1)
        {
            printf("Error: Camera is not active.\n");
            return;
        }

        //Convert to a vector in order to convert
        cv::vector<uchar> buf;
        for(int i = 0; i < size; ++i)
        {
            buf.push_back((uchar)message[i]);
        }
    
        printf("camera = %d\nquality = %d\ninvert = %d\nsize = %d\n\n", camera, quality, invert, size);

        //Incoming image is in jpg, convert to Mat
        matrix = cv::imdecode(buf, 1);

        if(invert)
        {
            matrix = flipMatrix(matrix);
        }
    
        //Save image
        imwrite(dest, matrix);
    
        //Convert to GL RGB format
        cv::cvtColor(matrix, matrix, CV_BGR2RGB);
        gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, matrix.cols, matrix.rows, GL_RGB, GL_UNSIGNED_BYTE, matrix.data);

        glutPostRedisplay();

        delete[] message;
    }
}

void printUsage()
{
    printf("Usage: server <portno> [<save_loc>]\n");
    return;
}

cv::Mat flipMatrix(cv::Mat mat)
{
    cv::Mat ans;

    cv::vector<cv::Mat> plane;
    split(mat,plane);

    if((int)plane.size() == 1)
    {
        ans = cv::Mat(mat.rows,mat.cols,CV_8UC1);
    }
    else
    {
        ans = cv::Mat(mat.rows,mat.cols,CV_8UC3);
    }

    cv::vector<cv::Mat> plane_a;
    split(ans,plane_a);

    for(int n = 0; n < (int)plane.size(); n++)
    {
        for(int r = 0; r < mat.rows; r++)
        {
            for(int c = 0; c < mat.cols; c++)
            {
                plane_a[n].at<uchar>(r,c) = plane[n].at<uchar>(mat.rows-r-1,mat.cols-c-1);
            }
        }
    }
    merge(plane_a,ans);
    return ans;
}

